{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# JavaScript 闭包\n",
    "- 闭包是一种保护私有变量的机制，在函数执行时形成私有的作用域，保护里面的私有变量不受外界干扰。\n",
    "- 直观的说就是形成一个不销毁的栈环境。\n",
    "\n",
    "JavaScript 变量可以是局部变量或全局变量。\n",
    "\n",
    "私有变量可以用到闭包。\n",
    "\n",
    "局部变量只能用于定义它函数内部。对于其他的函数或脚本代码是不可用的。\n",
    "\n",
    "全局和局部变量即便名称相同，它们也是两个不同的变量。修改其中一个，不会影响另一个的值。\n",
    "\n",
    "**变量声明时如果不使用 var 关键字，那么它就是一个全局变量，即便它在函数内定义。**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 变量生命周期\n",
    "全局变量的作用域是全局性的，即在整个JavaScript程序中，全局变量处处都在。\n",
    "\n",
    "而在函数内部声明的变量，只在函数内部起作用。这些变量是局部变量，作用域是局部性的；函数的参数也是局部性的，只在函数内部起作用。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 计数器困境\n",
    "设想下如果你想统计一些数值，且该计数器在所有函数中都是可用的。\n",
    "\n",
    "你可以使用全局变量，函数设置计数器递增："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "2\n",
      "3\n"
     ]
    }
   ],
   "source": [
    "var counter = 0;\n",
    " \n",
    "function add() {\n",
    "   return counter += 1;\n",
    "}\n",
    " \n",
    "console.log(add());\n",
    "console.log(add());\n",
    "console.log(add()); \n",
    "// 计数器现在为 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "计数器数值在执行 add() 函数时发生变化。\n",
    "\n",
    "但问题来了，页面上的任何脚本都能改变计数器，即便没有调用 add() 函数。\n",
    "\n",
    "如果我在函数内声明计数器，如果没有调用函数将无法修改计数器的值："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "1\n",
      "1\n"
     ]
    }
   ],
   "source": [
    "function add() {\n",
    "    var counter = 0;\n",
    "    return counter += 1;\n",
    "}\n",
    " \n",
    "console.log(add());\n",
    "console.log(add());\n",
    "console.log(add());\n",
    "// 本意是想输出 3, 但事与愿违，输出的都是 1 !"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "以上代码将无法正确输出，每次我调用 add() 函数，计数器都会设置为 1。\n",
    "\n",
    "**JavaScript 内嵌函数可以解决该问题。**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## JavaScript 内嵌函数\n",
    "所有函数都能访问全局变量。  \n",
    "\n",
    "实际上，在 JavaScript 中，所有函数都能访问它们上一层的作用域。\n",
    "\n",
    "JavaScript 支持嵌套函数。嵌套函数可以访问上一层的函数变量。\n",
    "\n",
    "该实例中，内嵌函数 `plus()` 可以访问父函数的 `counter` 变量："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "function add() {\n",
    "    var counter = 0;\n",
    "    function plus() {counter += 1;}\n",
    "    plus();    \n",
    "    return counter; \n",
    "}\n",
    "\n",
    "console.log(add());"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果我们能在外部访问 `plus()` 函数，这样就能解决计数器的困境。\n",
    "\n",
    "我们同样需要确保 `counter = 0` 只执行一次。\n",
    "\n",
    "**我们需要闭包。**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## JavaScript 闭包\n",
    "还记得函数自我调用吗？该函数会做什么？"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "var add = (()=>{\n",
    "    var counter = 0;\n",
    "    return ()=>{return counter += 1};\n",
    "})();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "console.log(add());\n",
    "console.log(add());\n",
    "console.log(add());"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 实例解析\n",
    "变量 add 指定了函数自我调用的返回字值。\n",
    "\n",
    "自我调用函数只执行一次。设置计数器为 0。并返回函数表达式。\n",
    "\n",
    "add变量可以作为一个函数使用。非常棒的部分是它可以访问函数上一层作用域的计数器。\n",
    "\n",
    "这个叫作 JavaScript **闭包**。它使得函数拥有私有变量变成可能。\n",
    "\n",
    "计数器受匿名函数的作用域保护，只能通过 add 方法修改。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 总结\n",
    "闭包就是一个函数引用另一个函数的变量，因为变量被引用着所以不会被回收，因此可以用来封装一个私有变量。这是优点也是缺点，不必要的闭包只会增加内存消耗。\n",
    "\n",
    "或者说闭包就是子函数可以使用父函数的局部变量，还有父函数的参数。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Javascript (Node.js)",
   "language": "javascript",
   "name": "javascript"
  },
  "language_info": {
   "file_extension": ".js",
   "mimetype": "application/javascript",
   "name": "javascript",
   "version": "13.13.0"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
